/*
* Copyright (c) 2015-2018 SHENZHEN TOMTOP SCIENCE AND TECHNOLOGY DEVELOP CO., LTD. All rights reserved.
*
* 注意：本内容仅限于深圳市通拓科技研发有限公司内部传阅，禁止外泄以及用于其他的商业目的 
*/
package com.jaws.core.common.util;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;

/**
 * 基础算法来源Twitter_Snowflake，
 * 本id生成算法基于获取每个服务 主机名取“-”后面位数作为workerId，例如：账户系统某个主机名为account-01,那么workerid为1
 * 主要适用于主机ip地址是乱序情况，例如：一个机器ip地址为  255.244.255.255  另一个 为255.255.244.255  
 * ip地址累加值相同，如果出现是同一个服务就会引起主键冲突问题；
 * 以主机名命名方式必须要求运维&研发人员把控好每个服务主机命名绝对不可用产生同名冲突或者命名错误问题，否则该算法将失效 
 * 
 *   
 * <br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0<br>
 * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)
 * 得到的值），这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdWorker类的startTime属性）。41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 *
 * 365) = 69<br>
 * 10位的数据机器位，可以部署在1024个节点，包括5位datacenterId和5位workerId<br>
 * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位，为一个Long型。<br>
 * 
 * @author lixin
 * @date 2018-03-06 09:02:24
 * @since TODO(说明当前修改版本号)
 */
public class HostNameKeyGeneratorUtil {
	 // 基准时间
    private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT
    // 区域标志位数
    private final static long regionIdBits = 3L;
    // 机器标识位数
    private final static long workerIdBits = 10L;
    // 序列号识位数
    private final static long sequenceBits = 10L;

    // 区域标志ID最大值
    private final static long maxRegionId = -1L ^ (-1L << regionIdBits);
    // 机器ID最大值
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 序列号ID最大值
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);

    // 机器ID偏左移10位
    private final static long workerIdShift = sequenceBits;
    // 业务ID偏左移20位
    private final static long regionIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移23位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;

    private static long lastTimestamp = -1L;

    private long sequence = 0L;
    
    /**
     * 第几个机器，数值不可用超过1023，从0开始，最多可以扩到第1024个机器
     */
    private long workerId = 0L;
    
    /**
     * 第几个机房 ，数值不可用超过7，从0开始，最多可以扩到8个机房
     */
    private long datacenterId = 0L;
    
    
	public static HostNameKeyGeneratorUtil snowflake = new HostNameKeyGeneratorUtil();

	static {
		if (snowflake == null) {
			snowflake = new HostNameKeyGeneratorUtil();
		}

	}

	public HostNameKeyGeneratorUtil() {

		this.workerId = getWorkerIdFromHostName();

	}
	
	/**
	 * 
	 * 通过获取主机名获取workerID
	 * 
	 * 取hostname最后一个“-”后面的数字作为workerId
	 * 
	 * @author lixin
	 * @date 2018-03-15 10:59:18
	 * @since 1.0
	 *
	 * @return
	 */
	private long getWorkerIdFromHostName() {

		InetAddress address;
        Long workerId;
        try {
            address = InetAddress.getLocalHost();
        } catch (final UnknownHostException e) {
            throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!");
        }
        String hostName = address.getHostName();
        try {
            workerId = Long.valueOf(hostName.replace(hostName.replaceAll("\\d+$", ""), ""));
        } catch (final NumberFormatException e) {
            throw new IllegalArgumentException(String.format("Wrong hostname:%s, hostname must be end with number!", hostName));
        }
        
		return workerId;
	}

    public static long generate() {
        return snowflake.nextId(false, 0);
    }

    /**
     * 实际产生代码的
     *
     * @param isPadding
     * @param busId
     * @return
     */
    private synchronized long nextId(boolean isPadding, long busId) {

        long timestamp = timeGen();
        long paddingnum = this.datacenterId;

        if (isPadding) {
            paddingnum = busId;
        }

        if (timestamp < lastTimestamp) {
        	
        	
        	long delay = lastTimestamp - timestamp;
        	
        	
        	// 如果时间偏差较小则等待
        	if (delay < 5) {
        		
        		try {
					Thread.sleep(delay);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
        		
        		timestamp = timeGen();
        	}
        	
        	//如果还没好则抛异常(后续加报警代码实现)
        	if (timestamp < lastTimestamp) {
        		 try {
                     throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
        	} else {
        		return generate();
        	}
        	
           
        }

        //如果上次生成时间和当前时间相同,在同一毫秒内
        if (lastTimestamp == timestamp) {
            //sequence自增，因为sequence只有10bit，所以和sequenceMask相与一下，去掉高位
            sequence = (sequence + 1) & sequenceMask;
            //判断是否溢出,也就是每毫秒内超过1024，当为1024时，与sequenceMask相与，sequence就等于0
            if (sequence == 0) {
                //自旋等待到下一毫秒
                timestamp = tailNextMillis(lastTimestamp);
            }
        } else {
            // 如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加,
            // 为了保证尾数随机性更大一些,最后一位设置一个随机数
            sequence = new SecureRandom().nextInt(10);
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;
    }

    // 防止产生的时间比之前的时间还要小（由于NTP回拨等问题）,保持增量的趋势.
    private long tailNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    // 获取当前的时间戳
    protected long timeGen() {
        return System.currentTimeMillis();
    }
    
    public static void main(String args[]) {
//    	SnowflakeUtil uid = new SnowflakeUtil(1023,7);
    	for (int i = 0; i < 100; i++) {
    		System.out.println(HostNameKeyGeneratorUtil.generate());
    	}
    }
}
